package com.guosen.zebra.framework.starter.sso.handler;

import com.guosen.zebra.framework.starter.sso.constant.SsoConstant;
import com.guosen.zebra.framework.starter.sso.dto.EacResponse;
import com.guosen.zebra.framework.starter.sso.properties.SsoProperties;
import com.guosen.zebra.framework.starter.sso.service.SsoCallBackService;
import com.guosen.zebra.framework.starter.sso.service.SsoService;
import com.guosen.zebra.framework.starter.sso.service.SsoSupportService;
import com.guosen.zebra.framework.starter.sso.utils.EACValidateUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

import javax.servlet.http.Cookie;
import java.net.URI;
import java.util.Map;
import java.util.Set;

/**
 * Sso登录控制器
 */
@Slf4j
public class SsoLoginHandler {

    private final SsoProperties ssoProperties;
    private final SsoService ssoService;
    private final SsoSupportService ssoSupportService;
    private final SsoCallBackService ssoCallBackService;

    public SsoLoginHandler(SsoProperties ssoProperties, SsoService ssoService,
                              SsoSupportService ssoSupportService, SsoCallBackService ssoCallBackService) {
        this.ssoProperties = ssoProperties;
        this.ssoService = ssoService;
        this.ssoSupportService = ssoSupportService;
        this.ssoCallBackService = ssoCallBackService;
    }

    /**
     * 当前系统SSO登录请求接口，返回自动提交登录页(autoPostLogin.html)
     * @param request request
     * @return 提交表单页
     */
    public ServerResponse ssoLogin(ServerRequest request) {
        String returnUrl = request.param("returnUrl").orElse("");
        ServerResponse.BodyBuilder response = ServerResponse.ok();
        return response.render(ssoService.submitFormToSsoPage(returnUrl, request, response), request.attributes());
    }

    /**
     * SSO系统验证通过之后的跳转
     * @param request request
     * @return 当前系统登录验证通过后的跳转页
     */
    public ServerResponse ssoReturn(ServerRequest request) {
        EacResponse eacResponse = EACValidateUtils.toEacResponse(request);
        EACValidateUtils.checkRequiredParams(eacResponse);
        ServerResponse.BodyBuilder response = ServerResponse.ok();
        if (!EACValidateUtils.validate(eacResponse, ssoProperties)) {
            String errorMsg = "Failed to validate EAC response, eacResponse: " + eacResponse;
            log.error(errorMsg);
            return response.render(validateFailedPage(request, response, eacResponse, errorMsg), request.attributes());
        }
        return buildServerResponse(request, response, eacResponse);
    }

    /**
     * 回调并返回错误页
     */
    private String validateFailedPage(ServerRequest request, ServerResponse.BodyBuilder response,
                                      EacResponse eacResponse, String errorMsg) {
        try {
            ssoCallBackService.handleSsoValidateFailed(request, response, eacResponse);
        } catch (Exception e) {
            String errorTip = "[handleSsoValidateFailed] invoke failed, message: " + e.getMessage();
            log.error(errorTip, e);
            errorMsg = errorMsg + "\n" + errorTip;
        }
        return ssoSupportService.errorPage(errorMsg, request);
    }

    private ServerResponse buildServerResponse(ServerRequest request, ServerResponse.BodyBuilder response,
                                               EacResponse eacResponse) {
        String returnUrl = ssoService.ssoReturn(request, response, eacResponse);
        if (returnUrl.startsWith(SsoConstant.REDIRECT_PREFIX)) {
            return buildRedirectResponse(request, response, returnUrl);
        } else {
            return response.render(returnUrl, request.attributes());
        }
    }

    private ServerResponse buildRedirectResponse(ServerRequest request, ServerResponse.BodyBuilder response,
                                                 String returnUrl) {
        ServerResponse builtResponse = response.build();
        // 舍去前缀
        returnUrl = returnUrl.substring(SsoConstant.REDIRECT_PREFIX.length());
        // 如果发现是重定向，重建response，转移cookies
        try {
            ServerResponse.BodyBuilder redirectResponse = ServerResponse
                    .status(HttpStatus.FOUND)
                    .location(new URI(returnUrl));
            transferCookies(builtResponse, redirectResponse);
            return redirectResponse.build();
        } catch (Exception e) {
            String errorMsg = "Failed to build redirect response, error message: " + e.getMessage();
            log.error(errorMsg, e);
            return response.render(ssoSupportService.errorPage(errorMsg, request));
        }
    }

    private void transferCookies(ServerResponse source, ServerResponse.BodyBuilder target) {
        Set<Map.Entry<String, Cookie>> cookies = source.cookies().toSingleValueMap().entrySet();
        for (Map.Entry<String, Cookie> cookie : cookies) {
            target.cookie(cookie.getValue());
        }
    }
}
